package org.anachronos.jeep.probe.runtime;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.AdviceAdapter;
import org.objectweb.asm.commons.Method;

public class BaseClassAdapter extends ClassAdapter {
	public static class BaseAdviceAdapter extends AdviceAdapter {
		protected final String probeMethod;
		protected final String probeClass;

		protected BaseAdviceAdapter(final String probeClass, final MethodVisitor mv, final int access, final String name,
				final String descriptor) {
			super(mv, access, name, descriptor);
			this.probeClass = probeClass;
			this.probeMethod = name;
		}
	}
	protected String currentClass;
	protected static final Map<String, Class<? extends AdviceAdapter>> INTERFACE_TO_ADVICE_ADAPTER_CLASS = new HashMap<String, Class<? extends AdviceAdapter>>();
	protected static final Map<Class<? extends AdviceAdapter>, Pattern> ADVICE_ADAPTER_CLASS_TO_METHOD_PATTERN = new HashMap<Class<? extends AdviceAdapter>, Pattern>();
	protected static final Map<Class<? extends AdviceAdapter>, Pattern> ADVICE_ADAPTER_CLASS_TO_DESCRIPTOR_PATTERN = new HashMap<Class<? extends AdviceAdapter>, Pattern>();
	private List<Class<AdviceAdapter>> adviceAdapterClasses = new ArrayList<Class<AdviceAdapter>>();

	protected static Method initMethodDesc(final Class<?> probeClazz, final String methodName) {
		try {
			final String methodDescriptor = Type.getMethodDescriptor(
					probeClazz.getMethod(methodName));
			return new Method(methodName, methodDescriptor);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public void visit(int version, int access, String name, String signature,
			String superName, String[] interfaces) {
		super.visit(version, access, name, signature, superName, interfaces);
		currentClass = name;
		for (final String iface : interfaces) {
			if (INTERFACE_TO_ADVICE_ADAPTER_CLASS.containsKey(iface)) {
				adviceAdapterClasses.add((Class<AdviceAdapter>) 
						INTERFACE_TO_ADVICE_ADAPTER_CLASS.get(iface));
			}
		}
	}

	@Override
	public MethodVisitor visitMethod(final int access, final String name, final String description,
			final String signature, final String[] exceptions) {
		final MethodVisitor mv = cv.visitMethod(access, name, description, signature, exceptions);
		final AdviceAdapter adviceAdapter;
		if (enableTracing(currentClass, name)) {
			adviceAdapter = createAdviceAdapter(mv, access, name, description);			
		} else {
			adviceAdapter = null;
		}
		return adviceAdapter == null ? mv : adviceAdapter;
	}

	protected boolean enableTracing(final String probeClass, final String probeMethod) {
		return true;
	}

	private AdviceAdapter createAdviceAdapter(final MethodVisitor mv, final int access, final String name,
			final String description) {
		try {
			Constructor<AdviceAdapter> constructor = null;
			for (final Class<AdviceAdapter> adviceAdapterClazz : adviceAdapterClasses) {
				final Pattern methodNamePattern = 
					ADVICE_ADAPTER_CLASS_TO_METHOD_PATTERN.get(adviceAdapterClazz);
				if (methodNamePattern.matcher(name).matches()) {
					constructor = adviceAdapterClazz.getConstructor(String.class, MethodVisitor.class, int.class, String.class, String.class);
					final Pattern methodDescriptorPattern =
						ADVICE_ADAPTER_CLASS_TO_DESCRIPTOR_PATTERN.get(adviceAdapterClazz);
					final boolean hasMatchingDescriptor = methodDescriptorPattern.pattern().equals(".*") 
						&& methodDescriptorPattern.matcher(description).matches();
					if (hasMatchingDescriptor) {
						break;
					}
				}
			}
			final AdviceAdapter adviceAdapter;
			if (constructor == null) {
				adviceAdapter = null;
			} else {
				adviceAdapter = constructor.newInstance(currentClass, mv, access, name, description);
			}
			return adviceAdapter;
		} catch (Exception e) {
			throw new IllegalStateException("Can't create advice adapter!", e);
		}
	}

	public BaseClassAdapter(final ClassVisitor cv) {
		super(cv);
	}
}
